﻿#region Copyright 2011 Mike Goatly
// This source is subject to the the MIT License (MIT)
// All rights reserved.
#endregion

namespace Lifti.Tests.Persistence.PageManagerTests.Transactional
{
    using System.Linq;
    using System.Transactions;

    using Lifti.Persistence;

    using Microsoft.VisualStudio.TestTools.UnitTesting;

    using Moq;

    /// <summary>
    /// Tests for the rolling back of the page manager.
    /// </summary>
    [TestClass]
    public class RollingBackTransaction : TransactionalPageManagerTestBase
    {
        /// <summary>
        /// Saving a populated index node page should call through to the data file manager, saving data in the expected format.
        /// </summary>
        [TestMethod]
        public void ShouldNotCallThroughToDataFileManagerIfTransactionRollsBack()
        {
            // Setup
            IndexNodeDataPage page;
            using (var scope = new TransactionScope())
            {
                // Execute
                page = (IndexNodeDataPage)this.PageManager.GetPage(0);
                page.AddEntry(new ItemReferenceIndexNodeEntry(7, 9, 12));
                page.AddEntry(new NodeReferenceIndexNodeEntry(7, 8, 'x'));
                this.PageManager.SavePage(page);

                // Transaction is not committed.
            }

            // Verify the underlying data is unaffected
            this.DataFileManager.VerifyPageBodyWritten(0, Times.Never());
            VerifyPersistedData(
                this.DataFileManager.GetPageData(0),
                Data.IndexNodePage(null, 2, 2, 2, 6, Data.PageHeaderSize + 13 + 10).ItemReference(2, 5, 88).NodeReference(6, 4, 'a'));

            // Verify the page was not logged to the transaction
            this.LogFactory.Verify(f => f.CreateTransactionLog(It.IsAny<string>(), this.PageManager), Times.Exactly(1));
            this.TransactionLog.VerifySet(l => l.PageManagerHeaderWritten = true, Times.Never());
            this.TransactionLog.Verify(l => l.RegisterAffectedPage(page, PageWriteLevels.Header), Times.Never());
            this.TransactionLog.Verify(l => l.RegisterAffectedPage(page, PageWriteLevels.Body), Times.Exactly(1));

            // Verify the page has been rolled back
            this.TransactionLogRollback.Verify(r => r.Rollback(), Times.Exactly(1));
            page = (IndexNodeDataPage)this.PageManager.GetPage(0);
            Assert.AreEqual(2, page.Header.EntryCount);
            Assert.IsTrue((new[] { 5, 4 }).SequenceEqual(page.Entries.Select(e => e.ReferencedId)));
        }

        /// <summary>
        /// If a transaction causes the underlying data file to be extended and it is subsequently rolled back, the
        /// created pages should be rolled back.
        /// </summary>
        [TestMethod]
        public void ShouldRollbackCreatedPagesIfTransactionRollsBack()
        {
            this.Settings.SetupGet(s => s.GrowPageCount).Returns(2);

            // Setup
            ItemIndexDataPage<int> page;
            using (var scope = new TransactionScope())
            {
                // Execute
                page = (ItemIndexDataPage<int>)this.PageManager.GetPage(1);

                // There are no free pages so creating a new page will cause the extent to grow
                var newPage = this.PageManager.CreatePage(page);
                newPage.AddEntry(new ItemEntry<int>(5, 5, 4));

                this.PageManager.SavePage(page);
                this.PageManager.SavePage(newPage);

                // Transaction is not committed.
            }

            Assert.AreEqual(5, this.PageManager.TotalPageCount);
            Assert.AreEqual(1, this.PageManager.ItemDataPages.Count);
            Assert.AreEqual(3, this.PageManager.IndexNodeDataPages.Count);
            Assert.AreEqual(1, this.PageManager.ItemNodeIndexDataPages.Count);
            Assert.AreEqual(0, this.PageManager.UnusedPageCount);

            this.TransactionLogRollback.Verify(r => r.Rollback(), Times.Exactly(1));
        }

        /// <summary>
        /// If a page is invalidated, it should be rolled back to its previous state.
        /// </summary>
        [TestMethod]
        public void ShouldRollBackInvalidatedPages()
        {
            IndexNodeDataPage page;
            using (var scope = new TransactionScope())
            {
                // Execute
                page = (IndexNodeDataPage)this.PageManager.GetPage(0);

                // Remove all the entries in the page and save it to invalidate it. This will
                // change the first page entry in the page manager header
                page.RemoveEntry(e => true);
                this.PageManager.SavePage(page);
                Assert.AreEqual(DataPageType.Unused, page.Header.DataPageType);

                // Transaction is not committed.
            }

            Assert.AreEqual(5, this.PageManager.TotalPageCount);
            Assert.AreEqual(1, this.PageManager.ItemDataPages.Count);
            Assert.AreEqual(3, this.PageManager.IndexNodeDataPages.Count);
            Assert.AreEqual(1, this.PageManager.ItemNodeIndexDataPages.Count);
            Assert.AreEqual(0, this.PageManager.UnusedPageCount);

            page = (IndexNodeDataPage)this.PageManager.GetPage(0);
            Assert.AreEqual(DataPageType.IndexNode, page.Header.DataPageType);
            Assert.AreEqual(2, page.Entries.Count());

            this.TransactionLogRollback.Verify(r => r.Rollback(), Times.Exactly(1));
        }
    }
}
